package de.joergviola.mobj.server;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.googlecode.objectify.NotFoundException;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyService;

import de.joergviola.mobj.client.remote.RemoteTransaction;
import de.joergviola.mobj.shared.Filter;
import de.joergviola.mobj.shared.ObjectNotFoundException;
import de.joergviola.mobj.shared.Persistable;
import de.joergviola.mobj.shared.Query;
import de.joergviola.mobj.shared.Schema;
import de.joergviola.mobj.shared.UnknownEntityException;

/**
 * The server side implementation of the RPC service.
 */
public class RemoteTransactionImpl extends RemoteServiceServlet implements
		RemoteTransaction {

	private static final long serialVersionUID = -6010608298450803916L;
	private static final Logger log = Logger.getLogger("RemoteTransaction");
	private static boolean isInitialized = false;
	private final Schema schema;

	public RemoteTransactionImpl(Schema schema) {
		this.schema = schema;
	}

	private void initialize() {
		if (!isInitialized) {
			synchronized (RemoteTransactionImpl.class) {
				if (!isInitialized) {
					log.info("Initializing...");
					ObjectifyService.factory().getConversions()
							.add(new PersistableConverter());
					log.info("PersistableConverter registered");
					for (Class<?> type : schema.getTypes()) {
						ObjectifyService.register(type);
						log.info("Type registered: " + type.getName());
					}
					isInitialized = true;
				}
			}
		}
	}

	@Override
	public <T extends Persistable> T insert(T t) {
		log.info("Insert object of " + t.getClass().getSimpleName());
		initialize();
		Objectify ofy = ObjectifyService.begin();
		ofy.put(t);
		return t;
	}

	@Override
	public <T extends Persistable> void update(T t) {
		log.info("Update object of " + t.getClass().getSimpleName());
		initialize();
		Objectify ofy = ObjectifyService.begin();
		ofy.put(t);
	}

	@Override
	public <T extends Persistable> T get(String className, Long id)
			throws ObjectNotFoundException, UnknownEntityException {
		log.info("Get object " + className + ":" + id);
		initialize();
		Objectify ofy = ObjectifyService.begin();
		try {
			@SuppressWarnings("unchecked")
			Class<T> clazz = (Class<T>) Class.forName(className);
			T t = ofy.get(clazz, id.longValue());
			return t;
		} catch (NotFoundException e) {
			log.log(Level.SEVERE, "Object not found: " + className + ":" + id);
			throw new ObjectNotFoundException(id);
		} catch (ClassNotFoundException e) {
			log.log(Level.SEVERE, "Entity not found: " + className);
			throw new UnknownEntityException(className);
		}
	}

	@Override
	public <T extends Persistable> List<T> get(String className, List<Long> ids)
			throws ObjectNotFoundException, UnknownEntityException {
		log.info("Get objects " + className + ":" + ids);
		initialize();
		Objectify ofy = ObjectifyService.begin();
		try {
			@SuppressWarnings("unchecked")
			Class<T> clazz = (Class<T>) Class.forName(className);
			Map<Long, T> map = ofy.get(clazz, ids);
			ArrayList<T> result = new ArrayList<T>(map.values());
			return result;
		} catch (NotFoundException e) {
			log.log(Level.SEVERE, "Objects not found: " + className + ":" + ids);
			throw new ObjectNotFoundException(ids);
		} catch (ClassNotFoundException e) {
			log.log(Level.SEVERE, "Entity not found: " + className);
			throw new UnknownEntityException(className);
		}
	}

	@Override
	public <T extends Persistable> List<T> find(String className, Query query)
			throws ObjectNotFoundException, UnknownEntityException {
		log.info("Find objects " + className + " where " + query.toString());
		initialize();
		Objectify ofy = ObjectifyService.begin();
		try {
			@SuppressWarnings("unchecked")
			Class<T> clazz = (Class<T>) Class.forName(className);
			com.googlecode.objectify.Query<T> q = ofy.query(clazz);
			for (Filter filter : query.getFilter()) {
				q = q.filter(filter.getField(), filter.getValue());
			}
			Iterable<T> i = q.fetch();
			ArrayList<T> result = new ArrayList<T>();
			for (T t : i) {
				result.add(t);
			}
			log.info("Found objects:" + result.size());
			return result;
		} catch (NotFoundException e) {
			log.log(Level.SEVERE, "Objects not found: " + className + ":"
					+ query.toString());
			throw new ObjectNotFoundException(query);
		} catch (ClassNotFoundException e) {
			log.log(Level.SEVERE, "Entity not found: " + className);
			throw new UnknownEntityException(className);
		}
	}

	@Override
	public <T extends Persistable> void delete(T t) {
		log.info("Delete object of " + t.getClass().getSimpleName());
		initialize();
		Objectify ofy = ObjectifyService.begin();
		ofy.delete(t);
	}
}
